/*
 *  tracker/PatternEditorControlEventListener.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *  PatternEditorControlEventListener.cpp
 *  MilkyTracker
 *
 *  Created by Peter Barth on Fri Mar 11 2005.
 *
 */

#include "PatternEditorControl.h"
#include "Event.h"
#include "Screen.h"
#include "Font.h"
#include "ScrollBar.h"
#include "Menu.h"
#include "XModule.h"
#include "ContextMenu.h"
#include "Undo.h"

#define SCROLLBARWIDTH SCROLLBUTTONSIZE

pp_int32 PatternEditorControl::dispatchEvent(PPEvent* event)
{
    if (pattern == NULL)
        return -1;

    if (eventListener == NULL)
        return -1;

    switch (event->getID())
    {
        case eFocusGained:
        case eFocusGainedNoRepaint:
        {
            hasFocus = true;
            if (menuInvokeChannel != -1)
            {
                lastMenuInvokeChannel = menuInvokeChannel;
                menuInvokeChannel = -1;
                //parentScreen->paintControl(this);
            }

            if (event->getID() == eFocusGained)
                parentScreen->paintControl(this);

            //resetKeyModifier();
            goto leave;
        }

        case eFocusLost:
        {
            hasFocus = false;
            parentScreen->paintControl(this);
            //resetKeyModifier();
            goto leave;
        }

        case eFocusLostNoRepaint:
        {
            hasFocus = false;
            //resetKeyModifier();
            goto leave;
        }

        case eMouseWheelMoved:
        {
            TMouseWheelEventParams* params = (TMouseWheelEventParams*)event->getDataPtr();

            if (params->delta > 0)
            {
                PPEvent e(eBarScrollUp);
                handleEvent(reinterpret_cast<PPObject*>(vLeftScrollbar), &e);
            }
            else if (params->delta < 0)
            {
                PPEvent e(eBarScrollDown);
                handleEvent(reinterpret_cast<PPObject*>(vLeftScrollbar), &e);
            }

            event->cancel();

            break;
        }

        case eRMouseDown:
        {
            PPPoint* p = (PPPoint*)event->getDataPtr();

            if (hTopScrollbar->hit(*p))
            {
                if (controlCaughtByLMouseButton)
                    break;
                controlCaughtByRMouseButton = true;
                caughtControl = hTopScrollbar;
                caughtControl->dispatchEvent(event);
            }
            else if (hBottomScrollbar->hit(*p))
            {
                if (controlCaughtByLMouseButton)
                    break;
                controlCaughtByRMouseButton = true;
                caughtControl = hBottomScrollbar;
                caughtControl->dispatchEvent(event);
            }
            // Clicked on vertical scrollbar -> route event to scrollbar and catch scrollbar control
            else if (vLeftScrollbar->hit(*p))
            {
                if (controlCaughtByLMouseButton)
                    break;
                controlCaughtByRMouseButton = true;
                caughtControl = vLeftScrollbar;
                caughtControl->dispatchEvent(event);
            }
            else if (vRightScrollbar->hit(*p))
            {
                if (controlCaughtByLMouseButton)
                    break;
                controlCaughtByRMouseButton = true;
                caughtControl = vRightScrollbar;
                caughtControl->dispatchEvent(event);
            }
            else
            {
                RMouseDownInChannelHeading = -1;
                lastAction = RMouseDownActionFirstRClick;

                PPPoint cp = *p;

                cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
                cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

                if (cp.x < 0)
                    break;

                // right click on channel number
                if ((RMouseDownInChannelHeading = pointInChannelHeading(cp)) != -1)
                {
                    goto leave;
                }
                else if (cp.y < 0) break;

                pp_int32 i = (cp.x / slotSize) + startPos;
                if (i < 0 || i > patternEditor->getNumChannels() - 1)
                    break;

                invokeMenu(i, *p);
            }

            goto leave;
        }

        case eRMouseUp:
        {
            controlCaughtByRMouseButton = false;
            if (caughtControl && !controlCaughtByLMouseButton && !controlCaughtByRMouseButton)
            {
                caughtControl->dispatchEvent(event);
                caughtControl = NULL;
                break;
            }

            PPPoint cp = *((PPPoint*)event->getDataPtr());

            cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
            cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

            if (cp.x < 0 || RMouseDownInChannelHeading == -1)
                break;

            // right click on channel number
            if ((menuInvokeChannel = pointInChannelHeading(cp)) != -1 &&
                menuInvokeChannel == RMouseDownInChannelHeading &&
                lastAction == RMouseDownActionFirstRClick)
            {
                RMouseDownInChannelHeading = -1;
                parentScreen->paintControl(this);
                executeMenuCommand(MenuCommandIDMuteChannel);
                menuInvokeChannel = lastMenuInvokeChannel = -1;
                parentScreen->paintControl(this);
                goto leave;
            }
            RMouseDownInChannelHeading = -1;
            menuInvokeChannel = -1;
            parentScreen->paintControl(this);

            break;
        }

        case eLMouseDown:
        {
            hasDragged = false;

            PPPoint* p = (PPPoint*)event->getDataPtr();

            // Clicked on horizontal scrollbar -> route event to scrollbar and catch scrollbar control
            if (hTopScrollbar->hit(*p))
            {
                if (controlCaughtByRMouseButton)
                    break;
                controlCaughtByLMouseButton = true;
                caughtControl = hTopScrollbar;
                caughtControl->dispatchEvent(event);
            }
            else if (hBottomScrollbar->hit(*p))
            {
                if (controlCaughtByRMouseButton)
                    break;
                controlCaughtByLMouseButton = true;
                caughtControl = hBottomScrollbar;
                caughtControl->dispatchEvent(event);
            }
            // Clicked on vertical scrollbar -> route event to scrollbar and catch scrollbar control
            else if (vLeftScrollbar->hit(*p))
            {
                if (controlCaughtByRMouseButton)
                    break;
                controlCaughtByLMouseButton = true;
                caughtControl = vLeftScrollbar;
                caughtControl->dispatchEvent(event);
            }
            else if (vRightScrollbar->hit(*p))
            {
                if (controlCaughtByRMouseButton)
                    break;
                controlCaughtByLMouseButton = true;
                caughtControl = vRightScrollbar;
                caughtControl->dispatchEvent(event);
            }
            // Clicked in client area
            else
            {
                PPPoint cp = *p;

                cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
                cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

                if (cp.y < -((pp_int32)font->getCharHeight() + 6))
                    break;

                if (cp.x < -(getRowCountWidth() + 4))
                    break;

                // select new row by clicking on row number
                if (cp.x < -3)
                {
                    pp_int32 newStartIndex = cp.y / font->getCharHeight();
                    pp_int32 newStartPos = cp.x / slotSize;

                    pp_int32 visibleRows = (visibleHeight) / font->getCharHeight();
                    pp_int32 visibleChannels = (visibleWidth) / slotSize;

                    // copy of current selection
                    patternEditor->getSelection().backup();

                    preCursor = patternEditor->getCursor();

                    if (newStartIndex < visibleRows && newStartIndex >= 0)
                    {
                        if (newStartIndex + startIndex < 0)
                            break;

                        preCursor.row = newStartIndex + startIndex;
                    }

                    ppreCursor = &preCursor;
                    break;
                }
                else if (cp.x < 0)
                {
                    break;
                }

                pp_int32 i;
                if ((i = pointInChannelHeading(cp)) != -1)
                {

                    // Special commands
                    // Right button is pressed and left button is pressed
                    if (::getKeyModifier() & KeyModifierALT)
                    {
                        RMouseDownInChannelHeading = i;
                        lastAction = RMouseDownActionSecondLClick;
                    }

                    if (RMouseDownInChannelHeading == i)
                    {
                        if (lastAction == RMouseDownActionFirstRClick ||
                            lastAction == RMouseDownActionSecondLClick)
                        {
                            if (isSoloChannel(i))
                                goto unmuteAll;
                            menuInvokeChannel = i;
                            executeMenuCommand(MenuCommandIDSoloChannel);
                            parentScreen->paintControl(this);
                            lastAction = RMouseDownActionFirstLClick;
                            hasDragged = true;
                            goto leave;
                        }
                        else if (lastAction == RMouseDownActionFirstLClick)
                        {
unmuteAll:
                            menuInvokeChannel = i;
                            executeMenuCommand(MenuCommandIDUnmuteAll);
                            parentScreen->paintControl(this);
                            lastAction = RMouseDownActionSecondLClick;
                            hasDragged = true;
                            goto leave;
                        }
                    }

                    if (i == lastMenuInvokeChannel)
                    {
                        lastMenuInvokeChannel = -1;
                        goto leave;
                    }

                    // show menu
                    pp_int32 menuPosY = location.y + SCROLLBARWIDTH + 1 + font->getCharHeight();
                    pp_int32 menuPosX = location.x + SCROLLBARWIDTH + getRowCountWidth() + 4 + slotSize * (i - startPos);

                    PPPoint p2(menuPosX, menuPosY);

                    invokeMenu(i, p2);
                    goto leave;
                }
                else if (cp.y < 0) break;

                pp_int32 newStartIndex = cp.y / font->getCharHeight();

                pp_int32 newStartPos = cp.x / slotSize;

                pp_int32 visibleRows = (visibleHeight) / font->getCharHeight();
                pp_int32 visibleChannels = (visibleWidth) / slotSize;

                // copy of current selection
                patternEditor->getSelection().backup();

                // If we're pressing the shift key start selection
                // at current cursor position
                if (::getKeyModifier() & selectionKeyModifier)
                {
                    if (patternEditor->getSelection().start.isValid())
                        patternEditor->getSelection().end = patternEditor->getCursor();
                    else
                        patternEditor->getSelection().start = patternEditor->getCursor();
                }

                preCursor = patternEditor->getCursor();

                if (newStartIndex < visibleRows && newStartIndex >= 0)
                {
                    if (newStartIndex + startIndex < 0)
                        break;

                    preCursor.row = newStartIndex + startIndex;
                }

                if (newStartPos < visibleHeight && newStartPos >= 0)
                {
                    selectionTicker = 0;
                    startSelection = true;

                    if (newStartPos + startPos < 0)
                        break;

                    preCursor.channel = newStartPos + startPos;

                    if (preCursor.channel >= patternEditor->getNumChannels())
                        break;

                    // start selecting row
                    if (!(::getKeyModifier() & selectionKeyModifier))
                    {
                        patternEditor->getSelection().start.channel = patternEditor->getSelection().end.channel = preCursor.channel;
                        patternEditor->getSelection().start.row = patternEditor->getSelection().end.row = preCursor.row;
                    }
                    else
                    {
                        patternEditor->getSelection().end.channel = preCursor.channel;
                        patternEditor->getSelection().end.row = preCursor.row;
                    }

                    pp_int32 innerPos = cp.x % slotSize;

                    preCursor.inner = 0;
                    if (!(::getKeyModifier() & selectionKeyModifier))
                        patternEditor->getSelection().start.inner = 0;
                    patternEditor->getSelection().end.inner = 0;
                    for (pp_uint32 i = 0; i < sizeof(cursorPositions) - 1; i++)
                    {
                        if (innerPos >= cursorPositions[i] &&
                            innerPos < cursorPositions[i + 1])
                        {
                            preCursor.inner = i;
                            if (!(::getKeyModifier() & selectionKeyModifier))
                                patternEditor->getSelection().start.inner = i;
                            patternEditor->getSelection().end.inner = i;
                            break;
                        }
                    }
                }

                ppreCursor = &preCursor;

                //parentScreen->paintControl(this);
            }

            break;
        }

        case eLMouseUp:
            controlCaughtByLMouseButton = false;
            if (caughtControl && !controlCaughtByLMouseButton && !controlCaughtByRMouseButton)
            {
                caughtControl->dispatchEvent(event);
                caughtControl = NULL;
                break;
            }

            menuInvokeChannel = -1;

            if (!hasDragged && !(::getKeyModifier() & selectionKeyModifier) && ppreCursor)
            {
                if (properties.clickToCursor)
                {
                    patternEditor->setCursor(*ppreCursor);

                    notifyUpdate(AdvanceCodeSelectNewRow);

                    switch (properties.scrollMode)
                    {
                        case ScrollModeToCenter:
                        case ScrollModeStayInCenter:
                            assureCursorVisible();
                            break;
                    }
                }

                patternEditor->resetSelection();
            }

            parentScreen->paintControl(this);

            startSelection = false;
            ppreCursor = NULL;

            break;

        case eLMouseDrag:
        {
            if (caughtControl && controlCaughtByLMouseButton)
            {
                caughtControl->dispatchEvent(event);
                break;
            }

            if (!startSelection)
                break;

            hasDragged = true;

            goto markSelection;
            //break;
        }

        case eRMouseDrag:
        {
            if (caughtControl && controlCaughtByRMouseButton)
                caughtControl->dispatchEvent(event);

            break;
        }

        case eRMouseRepeat:
        {
            if (caughtControl && controlCaughtByRMouseButton)
                caughtControl->dispatchEvent(event);

            break;
        }

        case eLMouseRepeat:
        {
            if (caughtControl && controlCaughtByLMouseButton)
            {
                caughtControl->dispatchEvent(event);
                break;
            }

            // for slowing down mouse pressed events
            selectionTicker++;

            if (selectionTicker & 1)
                break;

            // no selection has been made or mouse hasn't been dragged
            if (!startSelection || !hasDragged)
            {
                // mouse hasn't been dragged and selection ticker has reached threshold value
                if (!hasDragged && selectionTicker > 0)
                {
                    // get point
                    PPPoint* p = (PPPoint*)event->getDataPtr();

                    // make sure scrollbars weren't pressed
                    if (!hTopScrollbar->hit(*p) &&
                        !hBottomScrollbar->hit(*p) &&
                        !vLeftScrollbar->hit(*p) &&
                        !vRightScrollbar->hit(*p))
                    {
                        // translate into local coordinate system
                        PPPoint cp = *p;

                        cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
                        cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

                        // not in our local CS
                        if (cp.x < 0 || cp.y < 0)
                            break;

                        // valid channel?
                        pp_int32 i = (cp.x / slotSize) + startPos;
                        if (i < 0 || i > patternEditor->getNumChannels() - 1)
                            break;

                        // restore last selection
                        // will be made when mouse left mouse button is pressed
                        if (patternEditor->getSelection().isCopyValid())
                        {
                            patternEditor->getSelection().restore();
                        }

                        // No more cursor positioning
                        ppreCursor = NULL;

                        invokeMenu(i, *p);
                        // we're finished with event handling here
                        goto leave;
                    }
                }
                break;
            }

markSelection:
            PPPoint cp = *((PPPoint*)event->getDataPtr());

            PPPoint cp2 = cp;

            cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
            cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

            cp2.x -= location.x;
            cp2.y -= location.y;

            if (cp2.x > size.width - SCROLLBARWIDTH /*- (slotSize>>1)*/)
            {
                if (startPos + (visibleWidth / slotSize) < patternEditor->getNumChannels())
                    startPos++;
            }
            else if (cp2.x < SCROLLBARWIDTH + (signed)getRowCountWidth() + 4)
            {
                if (startPos > 0)
                    startPos--;
            }

            if (cp2.y > size.height - SCROLLBARWIDTH /*- ((signed)font->getCharHeight()*3)*/)
            {
                if (properties.scrollMode != ScrollModeStayInCenter)
                {
                    if (startIndex + (visibleHeight / font->getCharHeight()) < pattern->rows)
                        startIndex++;
                }
                else
                {
                    scrollCursorDown();
                    assureCursorVisible(true, false);
                }
            }
            else if (cp2.y < SCROLLBARWIDTH + ((signed)font->getCharHeight() * 4 /*+ 4*/))
            {
                if (properties.scrollMode != ScrollModeStayInCenter)
                {
                    if (startIndex > 0)
                        startIndex--;
                }
                else
                {
                    scrollCursorUp();
                    assureCursorVisible(true, false);
                }
            }

            if (cp.x < 0)
            {
                cp.x = 0;
                //goto leave;
            }

            if (cp.y < 0)
            {
                cp.y = 0;
                //goto leave;
            }

            pp_int32 newStartIndex = cp.y / font->getCharHeight();

            pp_int32 newStartPos = cp.x / slotSize;

            pp_int32 visibleRows = (visibleHeight) / font->getCharHeight();
            pp_int32 visibleChannels = (visibleWidth) / slotSize;

            //if (newStartIndex < visibleRows &&
            //	newStartPos < visibleChannels)
            //{
            mp_sint32 cursorPositionRow = newStartIndex + startIndex;
            mp_sint32 cursorPositionChannel = newStartPos + startPos;

            if (cursorPositionRow < 0) cursorPositionRow = 0;
            if (cursorPositionChannel < 0) cursorPositionChannel = 0;

            //if (cursorPositionRow < 0 || cursorPositionChannel < 0)
            //	break;

            if (cursorPositionChannel >= patternEditor->getNumChannels())
            {
                patternEditor->getSelection().end.channel = patternEditor->getNumChannels() - 1;
                patternEditor->getSelection().end.inner = 7;
            }
            else
            {

                // start selecting row
                patternEditor->getSelection().end.channel = cursorPositionChannel;
                patternEditor->getSelection().end.row = cursorPositionRow;

                pp_int32 innerPos = cp.x % slotSize;

                //selectionEnd.inner = 7;
                for (pp_uint32 i = 0; i < sizeof(cursorPositions) - 1; i++)
                {
                    if (innerPos >= cursorPositions[i] &&
                        innerPos < cursorPositions[i + 1])
                    {
                        patternEditor->getSelection().end.inner = i;
                        break;
                    }
                }
            }

            setScrollbarPositions(startIndex, startPos);

            //}

            parentScreen->paintControl(this);

            break;
        }

        case eKeyDown:
        {
            //assureCursorVisible();

            pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());
            pp_uint16 scanCode = *(((pp_uint16*)event->getDataPtr()) + 1);
            pp_uint16 character = *(((pp_uint16*)event->getDataPtr()) + 2);

            assureCursor = true;
            assureUpdate = false;

            bool keyboadStartSelectionFlipped = false;
            // start selection
            if (keyCode == VK_LEFT ||
                keyCode == VK_RIGHT ||
                keyCode == VK_UP ||
                keyCode == VK_DOWN ||
                keyCode == VK_PRIOR ||
                keyCode == VK_NEXT ||
                keyCode == VK_HOME ||
                keyCode == VK_END ||
                keyCode == VK_TAB)
            {
                if ((::getKeyModifier() == (unsigned)selectionKeyModifier) && keyboardStartSelection)
                {
                    patternEditor->getSelection().start = patternEditor->getSelection().end = patternEditor->getCursor();
                    keyboardStartSelection = false;
                    if (keyCode == VK_LEFT ||
                        keyCode == VK_RIGHT ||
                        keyCode == VK_UP ||
                        keyCode == VK_DOWN)
                    {
                        keyboadStartSelectionFlipped = true;
                    }
                }
            }

            switch (keyCode)
            {
                // store key modifiers
                case VK_ALT:
                    assureCursor = false;
                    if (selectionKeyModifier & KeyModifierALT)
                        keyboardStartSelection = true;
                    break;
                case VK_SHIFT:
                    assureCursor = false;
                    if (selectionKeyModifier & KeyModifierSHIFT)
                        keyboardStartSelection = true;
                    break;
                case VK_CONTROL:
                    assureCursor = false;
                    if (selectionKeyModifier & KeyModifierCTRL)
                        keyboardStartSelection = true;
                    break;

                default:
                {
                    bool res = executeBinding(scanCodeBindings, scanCode);

                    if (!res)
                        res = executeBinding(eventKeyDownBindings, keyCode);

                    if (!res)
                        handleKeyDown(keyCode, scanCode, character);

                    break;
                }
            }

            // normal selection
            if (keyCode == VK_LEFT ||
                keyCode == VK_RIGHT ||
                keyCode == VK_UP ||
                keyCode == VK_DOWN ||
                keyCode == VK_PRIOR ||
                keyCode == VK_NEXT ||
                keyCode == VK_HOME ||
                keyCode == VK_END ||
                keyCode == VK_TAB)
            {
                if ((::getKeyModifier() == (unsigned)selectionKeyModifier) &&
                    !keyboardStartSelection &&
                    !keyboadStartSelectionFlipped)
                {
                    patternEditor->getSelection().end = patternEditor->getCursor();
                }
                else if (::getKeyModifier() == (KeyModifierALT | KeyModifierSHIFT) &&
                         patternEditor->getSelection().start.isValid())
                {
                    patternEditor->getSelection().end = patternEditor->getCursor();
                }
            }

            if (assureCursor)
            {
                assureCursorVisible();
                parentScreen->paintControl(this);
            }
            else if (assureUpdate)
            {
                parentScreen->paintControl(this);
            }

            break;
        }

        case eKeyUp:
        {
            pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());

            switch (keyCode)
            {
                case VK_SHIFT:
                    if (selectionKeyModifier & KeyModifierSHIFT)
                        keyboardStartSelection = false;
                    break;
                case VK_ALT:
                    if (selectionKeyModifier & KeyModifierALT)
                        keyboardStartSelection = false;
                    break;
                case VK_CONTROL:
                    if (selectionKeyModifier & KeyModifierCTRL)
                        keyboardStartSelection = false;
                    break;
            }
            break;
        }

        default:
            if (caughtControl == NULL)
                break;

            caughtControl->dispatchEvent(event);
            goto leave;

    }

leave:
    return 0;
}

pp_int32 PatternEditorControl::handleEvent(PPObject* sender, PPEvent* event)
{
    // Vertical scrollbar, scroll down
    if ((sender == reinterpret_cast<PPObject*>(vLeftScrollbar) ||
         sender == reinterpret_cast<PPObject*>(vRightScrollbar)) &&
        event->getID() == eBarScrollDown)
    {
        if (properties.scrollMode != ScrollModeStayInCenter)
        {
            pp_int32 visibleItems = (visibleHeight) / font->getCharHeight();

            if (startIndex + visibleItems < pattern->rows)
                startIndex++;

            float v = (float)(pattern->rows - visibleItems);

            vLeftScrollbar->setBarPosition((pp_int32)(startIndex * (65536.0f / v)));
            vRightScrollbar->setBarPosition((pp_int32)(startIndex * (65536.0f / v)));
        }
        else
        {
            scrollCursorDown();
            assureCursorVisible(true, false);
        }
    }
    // Vertical scrollbar, scroll up
    else if ((sender == reinterpret_cast<PPObject*>(vLeftScrollbar) ||
              sender == reinterpret_cast<PPObject*>(vRightScrollbar)) &&
             event->getID() == eBarScrollUp)
    {
        if (properties.scrollMode != ScrollModeStayInCenter)
        {
            if (startIndex)
                startIndex--;

            pp_int32 visibleItems = (visibleHeight) / font->getCharHeight();

            float v = (float)(pattern->rows - visibleItems);

            vLeftScrollbar->setBarPosition((pp_int32)(startIndex * (65536.0f / v)));
            vRightScrollbar->setBarPosition((pp_int32)(startIndex * (65536.0f / v)));
        }
        else
        {
            scrollCursorUp();
            assureCursorVisible(true, false);
        }
    }
    // Vertical scrollbar, position changed
    else if ((sender == reinterpret_cast<PPObject*>(vLeftScrollbar) ||
              sender == reinterpret_cast<PPObject*>(vRightScrollbar)) &&
             event->getID() == eBarPosChanged)
    {

        float pos = reinterpret_cast<PPScrollbar*>(sender)->getBarPosition() / 65536.0f;

        switch (properties.scrollMode)
        {
            case ScrollModeToEnd:
            case ScrollModeToCenter:
            {
                pp_int32 visibleItems = (visibleHeight) / font->getCharHeight();
                float v = (float)(pattern->rows - visibleItems);
                startIndex = (pp_uint32)(v * pos);
                vLeftScrollbar->setBarPosition((pp_int32)(pos * 65536.0f));
                vRightScrollbar->setBarPosition((pp_int32)(pos * 65536.0f));
                break;
            }
            case ScrollModeStayInCenter:
                patternEditor->getCursor().row = (pp_int32)(pos * (pattern->rows - 1));
                assureCursorVisible(true, false);
                break;
        }
    }
    // Horizontal scrollbar, scroll up (=left)
    else if ((sender == reinterpret_cast<PPObject*>(hBottomScrollbar) ||
              sender == reinterpret_cast<PPObject*>(hTopScrollbar)) &&
             event->getID() == eBarScrollUp)
    {
        if (startPos)
            startPos--;

        pp_int32 visibleItems = (visibleWidth) / slotSize;

        float v = (float)(patternEditor->getNumChannels() - visibleItems);

        hTopScrollbar->setBarPosition((pp_int32)(startPos * (65536.0f / v)));
        hBottomScrollbar->setBarPosition((pp_int32)(startPos * (65536.0f / v)));
    }
    // Horizontal scrollbar, scroll down (=right)
    else if ((sender == reinterpret_cast<PPObject*>(hBottomScrollbar) ||
              sender == reinterpret_cast<PPObject*>(hTopScrollbar)) &&
             event->getID() == eBarScrollDown)
    {
        pp_int32 visibleItems = (visibleWidth) / slotSize;

        if (startPos + visibleItems < (signed)patternEditor->getNumChannels())
            startPos++;

        float v = (float)(patternEditor->getNumChannels() - visibleItems);

        if (v < 0.0f)
            v = 0.0f;

        hTopScrollbar->setBarPosition((pp_int32)(startPos * (65536.0f / v)));
        hBottomScrollbar->setBarPosition((pp_int32)(startPos * (65536.0f / v)));
    }
    // Horizontal scrollbar, position changed
    else if ((sender == reinterpret_cast<PPObject*>(hBottomScrollbar) ||
              sender == reinterpret_cast<PPObject*>(hTopScrollbar)) &&
             event->getID() == eBarPosChanged)
    {

        float pos = reinterpret_cast<PPScrollbar*>(sender)->getBarPosition() / 65536.0f;

        pp_int32 visibleItems = (visibleWidth) / slotSize;

        float v = (float)(patternEditor->getNumChannels() - visibleItems);

        if (v < 0.0f)
            v = 0.0f;

        startPos = (pp_uint32)(v * pos);

        hTopScrollbar->setBarPosition((pp_int32)(pos * 65536.0f));
        hBottomScrollbar->setBarPosition((pp_int32)(pos * 65536.0f));
    }
    else if (sender == reinterpret_cast<PPObject*>(editMenuControl))
    {
        switch (event->getID())
        {
            case eCommand:
                executeMenuCommand(*((pp_int32*)event->getDataPtr()));
                break;
            case eRemovedContextMenu:
            {
                struct MetaData
                {
                    pp_int32 id;
                    PPPoint p;
                }* metaData;

                metaData = (MetaData*)event->getDataPtr();

                PPPoint cp(metaData->p);

                cp.x -= location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
                cp.y -= location.y + SCROLLBARWIDTH + font->getCharHeight() + 4;

                pp_int32 menuInvokeChannel = pointInChannelHeading(cp);
                if (menuInvokeChannel != -1 && menuInvokeChannel != this->menuInvokeChannel)
                {
                    PPEvent e((EEventDescriptor)metaData->id, &metaData->p, sizeof(PPPoint));
                    dispatchEvent(&e);
                }
                // clicked somewhere else in the control, no more channel header
                // highlight and repaint without updating
                else
                {
                    this->menuInvokeChannel = -1;
                    parentScreen->paintControl(this, false);
                }

                break;
            }
        }
        return 0;
    }
    else
    {
        return 0;
    }

    // Little happy workaround for scroll to center scrollmode
    if (properties.scrollMode == ScrollModeToCenter)
    {
        pp_int32 visibleItems = (visibleHeight) / font->getCharHeight();
        if ((vLeftScrollbar->getBarPosition() == 65536 ||
             vRightScrollbar->getBarPosition() == 65536) &&
            startIndex + visibleItems <= pattern->rows)
        {
            startIndex++;
        }
    }

    parentScreen->paintControl(this);

    return 0;
}


pp_int32 PatternEditorControl::pointInChannelHeading(PPPoint& cp)
{
    if (cp.x < 0)
        return -1;

    // right click on channel numbber
    if (cp.y < 0 && cp.y < -2 && cp.y > -4 - (pp_int32)font->getCharHeight())
    {
        pp_int32 i = (cp.x / slotSize) + startPos;
        if (i < 0 || i > 31)
            return -1;

        return i;
    }

    return -1;
}

void PatternEditorControl::invokeMenu(pp_int32 channel, const PPPoint& p)
{
    menuInvokeChannel = channel;

    editMenuControl->setLocation(p);
    editMenuControl->setState(MenuCommandIDCut, !hasValidSelection());
    editMenuControl->setState(MenuCommandIDCopy, !hasValidSelection());
    editMenuControl->setState(MenuCommandIDPaste, patternEditor->clipBoardSelectionIsEmpty());
    editMenuControl->setState(MenuCommandIDPorousPaste, patternEditor->clipBoardSelectionIsEmpty());
    editMenuControl->setState(MenuCommandIDUndo, !patternEditor->canUndo());
    editMenuControl->setState(MenuCommandIDRedo, !patternEditor->canRedo());
    editMenuControl->setState(MenuCommandIDSwapChannels, menuInvokeChannel == patternEditor->getCursor().channel);
    parentScreen->setContextMenuControl(editMenuControl);
}

bool PatternEditorControl::isSoloChannel(pp_int32 c)
{
    pp_int32 i = 0;
    for (pp_int32 j = 0; j < patternEditor->getNumChannels(); j++)
        if (muteChannels[j])
            i++;

    if (!muteChannels[c] && i == patternEditor->getNumChannels() - 1)
        return true;

    return false;
}